package com.wiscamp.ninechapters.challenges.domain.services;

import com.wiscamp.ninechapters.challenges.domain.models.GroupUser;
import com.wiscamp.ninechapters.challenges.domain.models.InviteHelp;
import com.wiscamp.ninechapters.challenges.domain.models.UserGroup;
import com.wiscamp.ninechapters.challenges.domain.repositories.GroupsRepository;
import cube.ddd.aspect.AopServiceException;
import com.wiscamp.ninechapters.common.exception.ServiceException;
import com.wiscamp.ninechapters.ddd.DomainService;
import com.wiscamp.ninechapters.panels.domain.models.UserWechat;
import com.wiscamp.ninechapters.panels.domain.services.WechatUserService;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * User Group Service
 *
 * @author yifeng group
 * olympics maths challenge service
 */
@Service
public class GroupsService implements DomainService {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private GroupsRepository groupsRepository;

    @Autowired
    private WechatUserService wechatUserService;

    @Autowired
    public GroupsService(GroupsRepository groupsRepository, WechatUserService wechatUserService) {

        this.groupsRepository = groupsRepository;
        this.wechatUserService = wechatUserService;
    }

    /**
     * Creates a user group
     *
     * @param group group instance
     * @return group instance
     */
    @AopServiceException
    public UserGroup createUserGroup(@NotNull UserGroup group) {
        group.setCreateTime(LocalDateTime.now());
        group.setEditTime(LocalDateTime.now());
        group.setGroupCode("GROUP-" + group.getManagerId());
        var newGroup = groupsRepository.saveUserGroup(group);

        // 将自己加入团队
        var groupUser = new GroupUser();
        if (newGroup != null) {
            groupUser.setGroupId(newGroup.getGroupId());
            groupUser.setUserId(newGroup.getManagerId());
            groupsRepository.saveGroupUser(groupUser);
        }
        return newGroup;
    }

    /**
     * Gets a user group
     *
     * @param groupId group id
     * @return group instance
     */
    @AopServiceException
    public UserGroup getUserGroupById(@Min(0) long groupId) {
        return groupsRepository.getUserGroupById(groupId);
    }

    /**
     * Updates a user group
     *
     * @param userGroup
     * @param updateRank 是否更新排名
     * @return
     */

    @AopServiceException
    public UserGroup updateUserGroup(@NotNull UserGroup userGroup, boolean updateRank) {
        var newGroup = groupsRepository.saveUserGroup(userGroup);
        if (updateRank) {
            resortGroupPoints();
        }
        return newGroup;
    }

    /**
     * Resorts and updates groups rank
     */
    @AopServiceException
    public void resortGroupPoints() {
        var sort = Sort.by(Sort.Direction.DESC, "totalPoints");
        var pageable = PageRequest.of(0, 100, sort);
        Collection<UserGroup> sortedGroups = groupsRepository.findTop100OrderByTotalPoints(pageable);
        int counter = 0;
        for (var userPoints : sortedGroups) {
            userPoints.setTotalRank(counter + 1);
            counter++;
        }
        groupsRepository.saveUserGroups(sortedGroups);
        if (sortedGroups.size() == 100) {
            Collection<Long> groupIds = sortedGroups.stream().map(UserGroup::getGroupId).collect(Collectors.toList());
            groupsRepository.rankingAfter100ByPoints(groupIds);
        }
    }

    /**
     * Gets a user group by manager
     *
     * @param managerId team manager id
     * @return group instance
     */
    @AopServiceException
    public UserGroup getGroupByManager(@Min(0) long managerId) {
        return groupsRepository.getUserGroupByManager(managerId);
    }

    /**
     * Binds a user to a group
     *
     * @param groupUser relation instance between user and group
     * @return relation instance
     */
    @AopServiceException
    public GroupUser bindGroupUser(@NotNull GroupUser groupUser) {
        var found = getGroupUserByMember(groupUser.getUserId());
        if (Objects.nonNull(found)) {
            // if the user is a member of the group
            if (found.getGroupId() == groupUser.getGroupId()) {
                return found;
            } else {
                // the user has joined the other group
                var errorCode = "service-exception.user-group.user.found";
                throw new ServiceException(null, errorCode, groupUser.getUserId() + ":" + groupUser.getGroupId());
            }
        }
        return groupsRepository.saveGroupUser(groupUser);
    }

    /**
     * Get a group user by member
     *
     * @param userId
     * @return
     */

    @AopServiceException
    public GroupUser getGroupUserByMember(@Min(0) long userId) {
        return groupsRepository.findFirstMemberByUserId(userId);
    }

    /**
     * Gets a user group by member
     *
     * @param userId
     * @return
     */

    @AopServiceException
    public UserGroup getGroupByMember(@Min(0) long userId) {
        var groupUser = getGroupUserByMember(userId);
        if (groupUser == null)
            return null;
        return getUserGroupById(groupUser.getGroupId());
    }

    /**
     * Gets top 100 user groups
     *
     * @return
     */

    @Cacheable(value = "rank-group", key = "1")
    @AopServiceException
    public Collection<UserGroup> getTop100GroupBySortedPoints() {
        var sort = Sort.by(Sort.Direction.ASC, "totalPoints");
        var pageable = PageRequest.of(0, 100, sort);
        return groupsRepository.findTop100OrderByTotalPoints(pageable);
    }

    /**
     * Gets group users
     *
     * @param userId
     * @return
     */

    @AopServiceException
    public Collection<GroupUser> getGroupUsers(@Min(0) long userId) {
        var group = getGroupByMember(userId);
        if (group == null)
            return new ArrayList<>();
        var groupUsers = groupsRepository.getGroupUsers(group.getGroupId());
        // set avatar url
        Collection<Long> userIds = groupUsers.stream().map(GroupUser::getUserId).collect(Collectors.toList());
        Collection<UserWechat> userWechatList = wechatUserService.getUserWechatByIds(userIds);
        HashMap<Long, UserWechat> map = new HashMap<>();
        userWechatList.forEach(userWechat -> map.put(userWechat.getUserId(), userWechat));
        groupUsers.forEach(groupUser -> {
            var userWechat = map.get(groupUser.getUserId());
            if (userWechat != null) {
                groupUser.setNickName(userWechat.getNickName());
                groupUser.setAvatarUrl(userWechat.getAvatarUrl());
            }
        });
        return groupUsers;
    }

    /**
     * 组团邀请
     *
     * @param userId
     * @param supporterId
     * @return
     */

    @AopServiceException
    public GroupUser groupingInvite(@Min(0) long userId, @Min(0) long supporterId) {
        var userGroup = getGroupByManager(userId);
        if (Objects.isNull(userGroup))
            return null;
        var groupUser = new GroupUser();
        groupUser.setUserId(supporterId);
        groupUser.setGroupId(userGroup.getGroupId());
        return groupsRepository.saveGroupUser(groupUser);
    }

    /**
     * Creates an invitation help
     *
     * @param inviteHelp
     * @return
     */

    @AopServiceException
    public InviteHelp createInviteHelp(@NotNull InviteHelp inviteHelp) {
        var today = LocalDate.now();
        int year = today.getYear();
        int month = today.getMonthValue();
        int day = today.getDayOfMonth();

        // 邀请助力不能发送给自己
        if (inviteHelp.getSupporterId() == inviteHelp.getUserId()) {
            var errorCode = "service-exception.invite-help.supporter.equals";
            throw new ServiceException(null, errorCode, inviteHelp.getUserId() + ":" + inviteHelp.getSupporterId());
        }
        var foundedInviteHelp = groupsRepository.getInviteHelp(inviteHelp.getUserId(), year, month, day,
                inviteHelp.getSupporterId());
        // 该位朋友本日已助力过
        if (foundedInviteHelp != null) {
            var errorCode = "service-exception.invite-help.supporter.found";
            throw new ServiceException(null, errorCode, inviteHelp.getUserId() + ":" + inviteHelp.getSupporterId());
        }
        // 本日邀请助力已达到5人
        Collection<InviteHelp> invites = groupsRepository.getInviteHelps(inviteHelp.getUserId(), year, month,
                day);
        if (invites.size() >= 5) {
            var errorCode = "service-exception.invite-help.morethan.number";
            throw new ServiceException(null, errorCode, inviteHelp.getUserId() + ":" + inviteHelp.getSupporterId());
        }

        inviteHelp.setDateYear(year);
        inviteHelp.setDateMonth(month);
        inviteHelp.setDateDay(day);
        // updates friend‘s avatar
        var userWechat = wechatUserService.getUserWechatByUserId(inviteHelp.getSupporterId());
        if (userWechat != null)
            inviteHelp.setSupporterAvatar(userWechat.getAvatarUrl());

        return groupsRepository.saveInviteHelp(inviteHelp);
    }


    /**
     * Gets today's invitation helps
     *
     * @param userId
     * @return
     */

    @AopServiceException
    public Collection<InviteHelp> getInviteHelps(@Min(0) long userId) {
        var today = LocalDate.now();
        int year = today.getYear();
        int month = today.getMonthValue();
        int day = today.getDayOfMonth();
        Collection<InviteHelp> inviteHelps = groupsRepository.getInviteHelps(userId, year, month, day);
        Collection<Long> userIds = inviteHelps.stream().map(InviteHelp::getSupporterId)
                .collect(Collectors.toList());
        Collection<UserWechat> userWechatList = wechatUserService.getUserWechatByIds(userIds);
        HashMap<Long, UserWechat> map = new HashMap<>();
        userWechatList.forEach(userWechat -> map.put(userWechat.getUserId(), userWechat));
        inviteHelps.forEach(inviteHelp -> {
            var userWechat = map.get(inviteHelp.getSupporterId());
            if (userWechat != null) {
                inviteHelp.setSupporterNickName(userWechat.getNickName());
                inviteHelp.setSupporterAvatar(userWechat.getAvatarUrl());
            }
        });
        return inviteHelps;
    }

    /**
     * Calculates group's points
     *
     * @param userId
     * @param pointQty
     */

    public void calculateGroupPointsAndRank(@Min(0) long userId, int pointQty) {
        var groupUser = getGroupUserByMember(userId);
        if (groupUser == null)
            return;
        var userGroup = getUserGroupById(groupUser.getGroupId());
        if (Objects.isNull(userGroup))
            return;
        userGroup.setTotalPoints(userGroup.getTotalPoints() + pointQty);
        updateUserGroup(userGroup, true);
    }
}
